/*
 * Copyright 2019 ScyllaDB
 */

/*
 * This file is part of Scylla.
 *
 * Scylla is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Scylla is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with Scylla.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "expressions.hh"
#include "serialization.hh"
#include "base64.hh"
#include "conditions.hh"
#include "alternator/expressionsLexer.hpp"
#include "alternator/expressionsParser.hpp"
#include "utils/overloaded_functor.hh"
#include "error.hh"

#include "seastarx.hh"

#include <seastar/core/print.hh>
#include <seastar/util/log.hh>

#include <boost/algorithm/cxx11/any_of.hpp>
#include <boost/algorithm/cxx11/all_of.hpp>

#include <functional>
#include <unordered_map>

namespace alternator {

template <typename Func, typename Result = std::result_of_t<Func(expressionsParser&)>>
Result do_with_parser(std::string input, Func&& f) {
    expressionsLexer::InputStreamType input_stream{
        reinterpret_cast<const ANTLR_UINT8*>(input.data()),
        ANTLR_ENC_UTF8,
        static_cast<ANTLR_UINT32>(input.size()),
        nullptr };
    expressionsLexer lexer(&input_stream);
    expressionsParser::TokenStreamType tstream(ANTLR_SIZE_HINT, lexer.get_tokSource());
    expressionsParser parser(&tstream);

    auto result = f(parser);
    return result;
}

parsed::update_expression
parse_update_expression(std::string query) {
    try {
        return do_with_parser(query,  std::mem_fn(&expressionsParser::update_expression));
    } catch (...) {
        throw expressions_syntax_error(format("Failed parsing UpdateExpression '{}': {}", query, std::current_exception()));
    }
}

std::vector<parsed::path>
parse_projection_expression(std::string query) {
    try {
        return do_with_parser(query,  std::mem_fn(&expressionsParser::projection_expression));
    } catch (...) {
        throw expressions_syntax_error(format("Failed parsing ProjectionExpression '{}': {}", query, std::current_exception()));
    }
}

parsed::condition_expression
parse_condition_expression(std::string query) {
    try {
        return do_with_parser(query,  std::mem_fn(&expressionsParser::condition_expression));
    } catch (...) {
        throw expressions_syntax_error(format("Failed parsing ConditionExpression '{}': {}", query, std::current_exception()));
    }
}

namespace parsed {

void update_expression::add(update_expression::action a) {
    std::visit(overloaded_functor {
        [&] (action::set&)    { seen_set = true; },
        [&] (action::remove&) { seen_remove = true; },
        [&] (action::add&)    { seen_add = true; },
        [&] (action::del&)    { seen_del = true; }
    }, a._action);
    _actions.push_back(std::move(a));
}

void update_expression::append(update_expression other) {
    if ((seen_set && other.seen_set) ||
        (seen_remove && other.seen_remove) ||
        (seen_add && other.seen_add) ||
        (seen_del && other.seen_del)) {
        throw expressions_syntax_error("Each of SET, REMOVE, ADD, DELETE may only appear once in UpdateExpression");
    }
    std::move(other._actions.begin(), other._actions.end(), std::back_inserter(_actions));
    seen_set |= other.seen_set;
    seen_remove |= other.seen_remove;
    seen_add |= other.seen_add;
    seen_del |= other.seen_del;
}

void condition_expression::append(condition_expression&& a, char op) {
    std::visit(overloaded_functor {
        [&] (condition_list& x) {
            // If 'a' has a single condition, we could, instead of inserting
            // it insert its single condition (possibly negated if a._negated)
            // But considering it we don't evaluate these expressions many
            // times, this optimization is not worth extra code complexity.
            if (!x.conditions.empty() && x.op != op) {
                // Shouldn't happen unless we have a bug in the parser
                throw std::logic_error("condition_expression::append called with mixed operators");
            }
            x.conditions.push_back(std::move(a));
            x.op = op;
        },
        [&] (primitive_condition& x) {
            // Shouldn't happen unless we have a bug in the parser
            throw std::logic_error("condition_expression::append called on primitive_condition");
        }
    }, _expression);
}

} // namespace parsed

// The following resolve_*() functions resolve references in parsed
// expressions of different types. Resolving a parsed expression means
// replacing:
//  1. In parsed::path objects, replace references like "#name" with the
//     attribute name from ExpressionAttributeNames,
//  2. In parsed::constant objects, replace references like ":value" with
//     the value from ExpressionAttributeValues.
// These function also track which name and value references were used, to
// allow complaining if some remain unused.
// Note that the resolve_*() functions modify the expressions in-place,
// so if we ever intend to cache parsed expression, we need to pass a copy
// into this function.
//
// Doing the "resolving" stage before the evaluation stage has two benefits.
// First, it allows us to be compatible with DynamoDB in catching unused
// names and values (see issue #6572). Second, in the FilterExpression case,
// we need to resolve the expression just once but then use it many times
// (once for each item to be filtered).

static void resolve_path(parsed::path& p,
        const rjson::value* expression_attribute_names,
        std::unordered_set<std::string>& used_attribute_names) {
    const std::string& column_name = p.root();
    if (column_name.size() > 0 && column_name.front() == '#') {
        if (!expression_attribute_names) {
            throw api_error("ValidationException",
                    format("ExpressionAttributeNames missing, entry '{}' required by expression", column_name));
        }
        const rjson::value* value = rjson::find(*expression_attribute_names, column_name);
        if (!value || !value->IsString()) {
            throw api_error("ValidationException",
                    format("ExpressionAttributeNames missing entry '{}' required by expression", column_name));
        }
        used_attribute_names.emplace(column_name);
        p.set_root(std::string(rjson::to_string_view(*value)));
    }
}

static void resolve_constant(parsed::constant& c,
        const rjson::value* expression_attribute_values,
        std::unordered_set<std::string>& used_attribute_values) {
    std::visit(overloaded_functor {
        [&] (const std::string& valref) {
            if (!expression_attribute_values) {
                throw api_error("ValidationException",
                        format("ExpressionAttributeValues missing, entry '{}' required by expression", valref));
            }
            const rjson::value* value = rjson::find(*expression_attribute_values, valref);
            if (!value) {
                throw api_error("ValidationException",
                        format("ExpressionAttributeValues missing entry '{}' required by expression", valref));
            }
            if (value->IsNull()) {
                throw api_error("ValidationException",
                        format("ExpressionAttributeValues null value for entry '{}' required by expression", valref));
            }
            validate_value(*value, "ExpressionAttributeValues");
            used_attribute_values.emplace(valref);
            c.set(*value);
        },
        [&] (const parsed::constant::literal& lit) {
            // Nothing to do, already resolved
        }
    }, c._value);

}

void resolve_value(parsed::value& rhs,
        const rjson::value* expression_attribute_names,
        const rjson::value* expression_attribute_values,
        std::unordered_set<std::string>& used_attribute_names,
        std::unordered_set<std::string>& used_attribute_values) {
    std::visit(overloaded_functor {
        [&] (parsed::constant& c) {
            resolve_constant(c, expression_attribute_values, used_attribute_values);
        },
        [&] (parsed::value::function_call& f) {
            for (parsed::value& value : f._parameters) {
                resolve_value(value, expression_attribute_names, expression_attribute_values,
                        used_attribute_names, used_attribute_values);
            }
        },
        [&] (parsed::path& p) {
            resolve_path(p, expression_attribute_names, used_attribute_names);
        }
    }, rhs._value);
}

void resolve_set_rhs(parsed::set_rhs& rhs,
        const rjson::value* expression_attribute_names,
        const rjson::value* expression_attribute_values,
        std::unordered_set<std::string>& used_attribute_names,
        std::unordered_set<std::string>& used_attribute_values) {
    resolve_value(rhs._v1, expression_attribute_names, expression_attribute_values,
            used_attribute_names, used_attribute_values);
    if (rhs._op != 'v') {
        resolve_value(rhs._v2, expression_attribute_names, expression_attribute_values,
                used_attribute_names, used_attribute_values);
    }
}

void resolve_update_expression(parsed::update_expression& ue,
        const rjson::value* expression_attribute_names,
        const rjson::value* expression_attribute_values,
        std::unordered_set<std::string>& used_attribute_names,
        std::unordered_set<std::string>& used_attribute_values) {
    for (parsed::update_expression::action& action : ue.actions()) {
        resolve_path(action._path, expression_attribute_names, used_attribute_names);
        std::visit(overloaded_functor {
            [&] (parsed::update_expression::action::set& a) {
                resolve_set_rhs(a._rhs, expression_attribute_names, expression_attribute_values,
                        used_attribute_names, used_attribute_values);
            },
            [&] (parsed::update_expression::action::remove& a) {
                // nothing to do
            },
            [&] (parsed::update_expression::action::add& a) {
                resolve_constant(a._valref, expression_attribute_values, used_attribute_values);
            },
            [&] (parsed::update_expression::action::del& a) {
                resolve_constant(a._valref, expression_attribute_values, used_attribute_values);
            }
        }, action._action);
    }
}

static void resolve_primitive_condition(parsed::primitive_condition& pc,
        const rjson::value* expression_attribute_names,
        const rjson::value* expression_attribute_values,
        std::unordered_set<std::string>& used_attribute_names,
        std::unordered_set<std::string>& used_attribute_values) {
    for (parsed::value& value : pc._values) {
        resolve_value(value,
                expression_attribute_names, expression_attribute_values,
                used_attribute_names, used_attribute_values);
    }
}

void resolve_condition_expression(parsed::condition_expression& ce,
        const rjson::value* expression_attribute_names,
        const rjson::value* expression_attribute_values,
        std::unordered_set<std::string>& used_attribute_names,
        std::unordered_set<std::string>& used_attribute_values) {
    std::visit(overloaded_functor {
        [&] (parsed::primitive_condition& cond) {
            resolve_primitive_condition(cond,
                    expression_attribute_names, expression_attribute_values,
                    used_attribute_names, used_attribute_values);
        },
        [&] (parsed::condition_expression::condition_list& list) {
            for (parsed::condition_expression& cond : list.conditions) {
                resolve_condition_expression(cond,
                        expression_attribute_names, expression_attribute_values,
                            used_attribute_names, used_attribute_values);

            }
        }
    }, ce._expression);
}

void resolve_projection_expression(std::vector<parsed::path>& pe,
        const rjson::value* expression_attribute_names,
        std::unordered_set<std::string>& used_attribute_names) {
    for (parsed::path& p : pe) {
        resolve_path(p, expression_attribute_names, used_attribute_names);
    }
}

// condition_expression_on() checks whether a condition_expression places any
// condition on the given attribute. It can be useful, for example, for
// checking whether the condition tries to restrict a key column.

static bool value_on(const parsed::value& v, std::string_view attribute) {
    return std::visit(overloaded_functor {
        [&] (const parsed::constant& c) {
            return false;
        },
        [&] (const parsed::value::function_call& f) {
            for (const parsed::value& value : f._parameters) {
                if (value_on(value, attribute)) {
                    return true;
                }
            }
            return false;
        },
        [&] (const parsed::path& p) {
            return p.root() == attribute;
        }
    }, v._value);
}

static bool primitive_condition_on(const parsed::primitive_condition& pc, std::string_view attribute) {
    for (const parsed::value& value : pc._values) {
        if (value_on(value, attribute)) {
            return true;
        }
    }
    return false;
}

bool condition_expression_on(const parsed::condition_expression& ce, std::string_view attribute) {
    return std::visit(overloaded_functor {
        [&] (const parsed::primitive_condition& cond) {
            return primitive_condition_on(cond, attribute);
        },
        [&] (const parsed::condition_expression::condition_list& list) {
            for (const parsed::condition_expression& cond : list.conditions) {
                if (condition_expression_on(cond, attribute)) {
                    return true;
                }
            }
            return false;
        }
    }, ce._expression);
}

// The following calculate_value() functions calculate, or evaluate, a parsed
// expression. The parsed expression is assumed to have been "resolved", with
// the matching resolve_* function.

// Take two JSON-encoded list values (remember that a list value is
// {"L": [...the actual list]}) and return the concatenation, again as
// a list value.
static rjson::value list_concatenate(const rjson::value& v1, const rjson::value& v2) {
    const rjson::value* list1 = unwrap_list(v1);
    const rjson::value* list2 = unwrap_list(v2);
    if (!list1 || !list2) {
        throw api_error("ValidationException", "UpdateExpression: list_append() given a non-list");
    }
    rjson::value cat = rjson::copy(*list1);
    for (const auto& a : list2->GetArray()) {
        rjson::push_back(cat, rjson::copy(a));
    }
    rjson::value ret = rjson::empty_object();
    rjson::set(ret, "L", std::move(cat));
    return ret;
}

// calculate_size() is ConditionExpression's size() function, i.e., it takes
// a JSON-encoded value and returns its "size" as defined differently for the
// different types - also as a JSON-encoded number.
// It return a JSON-encoded "null" value if this value's type has no size
// defined. Comparisons against this non-numeric value will later fail.
static rjson::value calculate_size(const rjson::value& v) {
    // NOTE: If v is improperly formatted for our JSON value encoding, it
    // must come from the request itself, not from the database, so it makes
    // sense to throw a ValidationException if we see such a problem.
    if (!v.IsObject() || v.MemberCount() != 1) {
        throw api_error("ValidationException", format("invalid object: {}", v));
    }
    auto it = v.MemberBegin();
    int ret;
    if (it->name == "S") {
        if (!it->value.IsString()) {
            throw api_error("ValidationException", format("invalid string: {}", v));
        }
        ret = it->value.GetStringLength();
    } else if (it->name == "NS" || it->name == "SS" || it->name == "BS" || it->name == "L") {
        if (!it->value.IsArray()) {
            throw api_error("ValidationException", format("invalid set: {}", v));
        }
        ret = it->value.Size();
    } else if (it->name == "M") {
        if (!it->value.IsObject()) {
            throw api_error("ValidationException", format("invalid map: {}", v));
        }
        ret = it->value.MemberCount();
    } else if (it->name == "B") {
        if (!it->value.IsString()) {
            throw api_error("ValidationException", format("invalid byte string: {}", v));
        }
        ret = base64_decoded_len(rjson::to_string_view(it->value));
    } else {
        rjson::value json_ret = rjson::empty_object();
        rjson::set(json_ret, "null", rjson::value(true));
        return json_ret;
    }
    rjson::value json_ret = rjson::empty_object();
    rjson::set(json_ret, "N", rjson::from_string(std::to_string(ret)));
    return json_ret;
}

static const rjson::value& calculate_value(const parsed::constant& c) {
    return std::visit(overloaded_functor {
        [&] (const parsed::constant::literal& v) -> const rjson::value& {
            return *v;
        },
        [&] (const std::string& valref) -> const rjson::value& {
            // Shouldn't happen, we should have called resolve_value() earlier
            // and replaced the value reference by the literal constant.
            throw std::logic_error("calculate_value() called before resolve_value()");
        }
    }, c._value);
}

static rjson::value to_bool_json(bool b) {
    rjson::value json_ret = rjson::empty_object();
    rjson::set(json_ret, "BOOL", rjson::value(b));
    return json_ret;
}

static bool known_type(std::string_view type) {
    static thread_local const std::unordered_set<std::string_view> types = {
            "N", "S", "B", "NS", "SS", "BS", "L", "M", "NULL", "BOOL"
    };
    return types.contains(type);
}

using function_handler_type = rjson::value(calculate_value_caller, const rjson::value*, const parsed::value::function_call&);
static const
std::unordered_map<std::string_view, function_handler_type*> function_handlers {
    {"list_append", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::UpdateExpression) {
                throw api_error("ValidationException",
                        format("{}: list_append() not allowed here", caller));
            }
            if (f._parameters.size() != 2) {
                throw api_error("ValidationException",
                        format("{}: list_append() accepts 2 parameters, got {}", caller, f._parameters.size()));
            }
            rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
            rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
            return list_concatenate(v1, v2);
        }
    },
    {"if_not_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::UpdateExpression) {
                throw api_error("ValidationException",
                        format("{}: if_not_exists() not allowed here", caller));
            }
            if (f._parameters.size() != 2) {
                throw api_error("ValidationException",
                        format("{}: if_not_exists() accepts 2 parameters, got {}", caller, f._parameters.size()));
            }
            if (!std::holds_alternative<parsed::path>(f._parameters[0]._value)) {
                throw api_error("ValidationException",
                        format("{}: if_not_exists() must include path as its first argument", caller));
            }
            rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
            rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
            return v1.IsNull() ? std::move(v2) : std::move(v1);
        }
    },
    {"size", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::ConditionExpression) {
                throw api_error("ValidationException",
                        format("{}: size() not allowed here", caller));
            }
            if (f._parameters.size() != 1) {
                throw api_error("ValidationException",
                        format("{}: size() accepts 1 parameter, got {}", caller, f._parameters.size()));
            }
            rjson::value v = calculate_value(f._parameters[0], caller, previous_item);
            return calculate_size(v);
        }
    },
    {"attribute_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::ConditionExpressionAlone) {
                throw api_error("ValidationException",
                        format("{}: attribute_exists() not allowed here", caller));
            }
            if (f._parameters.size() != 1) {
                throw api_error("ValidationException",
                        format("{}: attribute_exists() accepts 1 parameter, got {}", caller, f._parameters.size()));
            }
            if (!std::holds_alternative<parsed::path>(f._parameters[0]._value)) {
                throw api_error("ValidationException",
                        format("{}: attribute_exists()'s parameter must be a path", caller));
            }
            rjson::value v = calculate_value(f._parameters[0], caller, previous_item);
            return to_bool_json(!v.IsNull());
        }
    },
    {"attribute_not_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::ConditionExpressionAlone) {
                throw api_error("ValidationException",
                        format("{}: attribute_not_exists() not allowed here", caller));
            }
            if (f._parameters.size() != 1) {
                throw api_error("ValidationException",
                        format("{}: attribute_not_exists() accepts 1 parameter, got {}", caller, f._parameters.size()));
            }
            if (!std::holds_alternative<parsed::path>(f._parameters[0]._value)) {
                throw api_error("ValidationException",
                        format("{}: attribute_not_exists()'s parameter must be a path", caller));
            }
            rjson::value v = calculate_value(f._parameters[0], caller, previous_item);
            return to_bool_json(v.IsNull());
        }
    },
    {"attribute_type", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::ConditionExpressionAlone) {
                throw api_error("ValidationException",
                        format("{}: attribute_type() not allowed here", caller));
            }
            if (f._parameters.size() != 2) {
                throw api_error("ValidationException",
                        format("{}: attribute_type() accepts 2 parameters, got {}", caller, f._parameters.size()));
            }
            // There is no real reason for the following check (not
            // allowing the type to come from a document attribute), but
            // DynamoDB does this check, so we do too...
            if (!f._parameters[1].is_constant()) {
                throw api_error("ValidationException",
                        format("{}: attribute_types()'s first parameter must be an expression attribute", caller));
            }
            rjson::value v0 = calculate_value(f._parameters[0], caller, previous_item);
            rjson::value v1 = calculate_value(f._parameters[1], caller, previous_item);
            if (v1.IsObject() && v1.MemberCount() == 1 && v1.MemberBegin()->name == "S") {
                // If the type parameter is not one of the legal types
                // we should generate an error, not a failed condition:
                if (!known_type(rjson::to_string_view(v1.MemberBegin()->value))) {
                    throw api_error("ValidationException",
                            format("{}: attribute_types()'s second parameter, {}, is not a known type",
                                    caller, v1.MemberBegin()->value));
                }
                if (v0.IsObject() && v0.MemberCount() == 1) {
                    return to_bool_json(v1.MemberBegin()->value == v0.MemberBegin()->name);
                } else {
                    return to_bool_json(false);
                }
            } else {
                throw api_error("ValidationException",
                        format("{}: attribute_type() second parameter must refer to a string, got {}", caller, v1));
            }
        }
    },
    {"begins_with", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::ConditionExpressionAlone) {
                throw api_error("ValidationException",
                        format("{}: begins_with() not allowed here", caller));
            }
            if (f._parameters.size() != 2) {
                throw api_error("ValidationException",
                        format("{}: begins_with() accepts 2 parameters, got {}", caller, f._parameters.size()));
            }
            rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
            rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
            // TODO: There's duplication here with check_BEGINS_WITH().
            // But unfortunately, the two functions differ a bit.

            // If one of v1 or v2 is malformed or has an unsupported type
            // (not B or S), what we do depends on whether it came from
            // the user's query (is_constant()), or the item. Unsupported
            // values in the query result in an error, but if they are in
            // the item, we silently return false (no match).
            bool bad = false;
            if (!v1.IsObject() || v1.MemberCount() != 1) {
                bad = true;
                if (f._parameters[0].is_constant()) {
                    throw api_error("ValidationException", format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v1));
                }
            } else if (v1.MemberBegin()->name != "S" && v1.MemberBegin()->name != "B") {
                bad = true;
                if (f._parameters[0].is_constant()) {
                    throw api_error("ValidationException", format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v1));
                }
            }
            if (!v2.IsObject() || v2.MemberCount() != 1) {
                bad = true;
                if (f._parameters[1].is_constant()) {
                    throw api_error("ValidationException", format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v2));
                }
            } else if (v2.MemberBegin()->name != "S" && v2.MemberBegin()->name != "B") {
                bad = true;
                if (f._parameters[1].is_constant()) {
                    throw api_error("ValidationException", format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v2));
                }
            }
            bool ret = false;
            if (!bad) {
                auto it1 = v1.MemberBegin();
                auto it2 = v2.MemberBegin();
                if (it1->name == it2->name) {
                    if (it2->name == "S") {
                        std::string_view val1 = rjson::to_string_view(it1->value);
                        std::string_view val2 = rjson::to_string_view(it2->value);
                        ret = val1.starts_with(val2);
                    } else /* it2->name == "B" */ {
                        ret = base64_begins_with(rjson::to_string_view(it1->value), rjson::to_string_view(it2->value));
                    }
                }
            }
            return to_bool_json(ret);
        }
    },
    {"contains", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) {
            if (caller != calculate_value_caller::ConditionExpressionAlone) {
                throw api_error("ValidationException",
                        format("{}: contains() not allowed here", caller));
            }
            if (f._parameters.size() != 2) {
                throw api_error("ValidationException",
                        format("{}: contains() accepts 2 parameters, got {}", caller, f._parameters.size()));
            }
            rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item);
            rjson::value v2 = calculate_value(f._parameters[1], caller, previous_item);
            return to_bool_json(check_CONTAINS(v1.IsNull() ? nullptr : &v1,  v2));
        }
    },
};

// Given a parsed::value, which can refer either to a constant value from
// ExpressionAttributeValues, to the value of some attribute, or to a function
// of other values, this function calculates the resulting value.
// "caller" determines which expression - ConditionExpression or
// UpdateExpression - is asking for this value. We need to know this because
// DynamoDB allows a different choice of functions for different expressions.
rjson::value calculate_value(const parsed::value& v,
        calculate_value_caller caller,
        const rjson::value* previous_item) {
    return std::visit(overloaded_functor {
        [&] (const parsed::constant& c) -> rjson::value {
            return rjson::copy(calculate_value(c));
        },
        [&] (const parsed::value::function_call& f) -> rjson::value {
            auto function_it = function_handlers.find(std::string_view(f._function_name));
            if (function_it == function_handlers.end()) {
                throw api_error("ValidationException",
                        format("UpdateExpression: unknown function '{}' called.", f._function_name));
            }
            return function_it->second(caller, previous_item, f);
        },
        [&] (const parsed::path& p) -> rjson::value {
            if (!previous_item) {
                return rjson::null_value();
            }
            std::string update_path = p.root();
            if (p.has_operators()) {
                // FIXME: support this
                throw api_error("ValidationException", "Reading attribute paths not yet implemented");
            }
            const rjson::value* previous_value = rjson::find(*previous_item, update_path);
            return previous_value ? rjson::copy(*previous_value) : rjson::null_value();
        }
    }, v._value);
}

// Same as calculate_value() above, except takes a set_rhs, which may be
// either a single value, or v1+v2 or v1-v2.
rjson::value calculate_value(const parsed::set_rhs& rhs,
        const rjson::value* previous_item) {
    switch(rhs._op) {
    case 'v':
        return calculate_value(rhs._v1, calculate_value_caller::UpdateExpression, previous_item);
    case '+': {
        rjson::value v1 = calculate_value(rhs._v1, calculate_value_caller::UpdateExpression, previous_item);
        rjson::value v2 = calculate_value(rhs._v2, calculate_value_caller::UpdateExpression, previous_item);
        return number_add(v1, v2);
    }
    case '-': {
        rjson::value v1 = calculate_value(rhs._v1, calculate_value_caller::UpdateExpression, previous_item);
        rjson::value v2 = calculate_value(rhs._v2, calculate_value_caller::UpdateExpression, previous_item);
        return number_subtract(v1, v2);
    }
    }
    // Can't happen
    return rjson::null_value();
}

} // namespace alternator
